########################################################################
#  Searx-Qt - Lightweight desktop application for Searx.
#  Copyright (C) 2020-2022  CYBERDEViL
#
#  This file is part of Searx-Qt.
#
#  Searx-Qt is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Searx-Qt is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################

from PyQt5.QtWidgets import (
    QWidget,
    QFrame,
    QVBoxLayout,
    QFormLayout,
    QGridLayout,
    QCheckBox,
    QLabel,
    QSpinBox,
    QDoubleSpinBox,
    QLineEdit,
    QComboBox,
    QHBoxLayout,
    QSizePolicy,
    QTabWidget,
    QPlainTextEdit,
    QSpacerItem
)

from PyQt5.QtCore import Qt, pyqtSignal, QVariant

from searxqt.views.guard import GuardSettings

from searxqt.widgets.buttons import Button
from searxqt.widgets.dialogs import UrlDialog

from searxqt.translations import _
from searxqt.themes import Themes
from searxqt.core import log
from searxqt.core.customAnchorCmd import AnchorCMD
from searxqt.core.images import ImagesSettings
from searxqt.core.requests import HAVE_SOCKS, ProxyProtocol


class ProxyWidget(QWidget):
    """
    @param model:
    @type model: searxqt.models.RequestSettingsModel
    """
    def __init__(self, model, parent=None):
        QWidget.__init__(self, parent=parent)

        self.__model = model

        layout = QFormLayout(self)

        self._proxyEnabledCheck = QCheckBox(self)
        self._proxyDNSCheck = QCheckBox(self)

        self._proxyType = QComboBox(self)
        self._proxyType.addItem("http")
        if HAVE_SOCKS:
            self._proxyType.addItem("socks4")
            self._proxyType.addItem("socks5")

        self._proxyStr = QLineEdit(self)
        self._proxyStr.setPlaceholderText(_("user:pass@host:port"))

        layout.addRow(QLabel(_("Enabled")), self._proxyEnabledCheck)
        layout.addRow(QLabel(_("Proxy DNS")), self._proxyDNSCheck)
        layout.addRow(QLabel(_("Protocol")), self._proxyType)
        layout.addRow(QLabel(_("Address")), self._proxyStr)

        # Set values
        self.updateFromModel()

        # Connections
        self._proxyEnabledCheck.toggled.connect(self.__enabledToggled)
        self._proxyDNSCheck.toggled.connect(self.__dnsToggled)
        self._proxyType.currentIndexChanged.connect(self.__protocolChanged)
        self._proxyStr.textEdited.connect(self.__hostTextEdited)

    def updateFromModel(self):
        model = self.__model
        self._proxyEnabledCheck.setChecked(model.proxyEnabled)
        self.enableWidgets(model.proxyEnabled)  # enable/disable widgets
        self._proxyDNSCheck.setChecked(model.proxyDNS)

        if model.proxyProtocol == ProxyProtocol.HTTP:
            self._proxyType.setCurrentIndex(0)
        elif HAVE_SOCKS:
            if model.proxyProtocol == ProxyProtocol.SOCKS4:
                self._proxyType.setCurrentIndex(1)
            elif model.proxyProtocol == ProxyProtocol.SOCKS5:
                self._proxyType.setCurrentIndex(2)

        self._proxyStr.setText(model.proxyHost)

    def __enabledToggled(self, state):
        self.__model.proxyEnabled = bool(state)
        self.enableWidgets(state)

    def enableWidgets(self, state):
        self._proxyDNSCheck.setEnabled(state)
        self._proxyType.setEnabled(state)
        self._proxyStr.setEnabled(state)

    def __dnsToggled(self, state):
        self.__model.proxyDNS = bool(state)

    def __protocolChanged(self, index):
        if index == 0:
            self.__model.proxyProtocol = ProxyProtocol.HTTP
            self._proxyDNSCheck.setEnabled(False)
            self._proxyDNSCheck.setToolTip(_("Not available for http proxy."))
            return
        elif index == 1:
            self.__model.proxyProtocol = ProxyProtocol.SOCKS4
        else:  # index == 2
            self.__model.proxyProtocol = ProxyProtocol.SOCKS5
        self._proxyDNSCheck.setEnabled(True)

    def __hostTextEdited(self, newText):
        # TODO verify format
        self.__model.proxyHost = newText


class TextEdit(QFrame):
    def __init__(self, model, parent=None):
        QFrame.__init__(self, parent)
        self._model = model
        layout = QVBoxLayout(self)
        buttonLayout = QHBoxLayout()

        self._textEdit = QPlainTextEdit(self)
        self._textEdit.setReadOnly(True)
        self._textEdit.setLineWrapMode(QPlainTextEdit.NoWrap)
        self._editButton = Button(_("Edit"), self)
        self._editButton.setCheckable(True)
        self._editButton.setChecked(False)
        self._cancelButton = Button(_("Cancel"), self)
        self._cancelButton.setCheckable(False)
        self._cancelButton.hide()

        buttonLayout.addWidget(self._cancelButton, 0, Qt.AlignLeft)
        buttonLayout.addWidget(self._editButton, 0, Qt.AlignLeft)
        buttonLayout.addStretch(1)

        layout.addWidget(self._textEdit)
        layout.addLayout(buttonLayout)

        self._editButton.toggled.connect(self.__toggleEdit)
        self._cancelButton.clicked.connect(self.__cancelEdit)

    def __toggleEdit(self, state):
        if state:
            self._editButton.setText(_("Save"))
            self._textEdit.setReadOnly(False)
            self._cancelButton.show()
        else:
            self._editButton.setText(_("Edit"))
            self._textEdit.setReadOnly(True)
            self._cancelButton.hide()
            self.updateToModel()

    def __cancelEdit(self, state):
        self._editButton.setText(_("Edit"))
        self._cancelButton.hide()
        self._textEdit.setReadOnly(True)
        self._editButton.setChecked(False)
        self.updateFromModel()

    def updateFromModel(self):
        pass  # reimplement this

    def updateToModel(self):
        pass  # reimplement this


class RequestsHeaderEdit(TextEdit):
    def __init__(self, model, parent=None):
        TextEdit.__init__(self, model, parent)

        warningLabel = QLabel(_("WARNING! Edit below only when you know what "
                                "you are doing!"), self)
        warningLabel.setWordWrap(True)
        self.layout().insertWidget(0, warningLabel)

        self.updateFromModel()

    def updateFromModel(self):
        text = ""
        for key, value in self._model.extraHeaders.items():
            text += f"\"{key}\" \"{value}\"\n"
        self._textEdit.setPlainText(text)

    def updateToModel(self):
        self._model.extraHeaders.clear()
        for line in self._textEdit.toPlainText().split("\n"):
            if " " not in line:
                continue
            key, value = line.split(" ", 1)
            value = value.lstrip().rstrip()

            if not key.startswith("\"") or not key.endswith("\""):
                # TODO malformed, inform the user
                continue

            if not value.startswith("\"") or not value.endswith("\""):
                # TODO malformed, inform the user
                continue

            key = key[1:len(key)-1]
            value = value[1:len(value)-1]

            if key.lower() == "user-agent":
                # TODO inform the user
                continue

            if "\"" in key:
                # TODO malformed, inform the user
                continue

            if "\"" in value:
                # TODO malformed, inform the user
                continue

            self._model.extraHeaders.update({key: value})
        self.updateFromModel()


class UserAgentsEdit(TextEdit):
    def __init__(self, model, parent=None):
        TextEdit.__init__(self, model, parent)

        self._textEdit.setToolTip(
            """- One user-agent string per line.
- Default user-agent string is the first (top) line.
- Empty lines will be removed.
- Leave empty to not send any user-agent string."""
        )

        self._randomUserAgent = QCheckBox(_("Random"), self)
        self._randomUserAgent.setToolTip(
            """When checked it will pick a random
user-agent from the list for each request."""
        )
        self.layout().addWidget(self._randomUserAgent)

        self.updateFromModel()

        self._randomUserAgent.stateChanged.connect(self._randomUserAgentEdited)

    def _randomUserAgentEdited(self, state):
        self._model.randomUserAgent = bool(state)

    def updateToModel(self):
        text = self._textEdit.toPlainText()
        self._model.useragents = [s for s in text.split('\n') if s]
        self.updateFromModel()

    def updateFromModel(self):
        text = ""
        for userAgentStr in self._model.useragents:
            if not text:
                text = userAgentStr
            else:
                text += f"\n{userAgentStr}"
        self._textEdit.setPlainText(text)


class RequestsSettings(QWidget):
    def __init__(self, model, parent=None):
        """
        @param model:
        @type model: searxqt.models.RequestSettingsModel
        """
        QWidget.__init__(self, parent=parent)

        self._model = model

        layout = QFormLayout(self)

        # Verify checkbox
        self._verifyCheck = QCheckBox(self)
        layout.addRow(QLabel(_("Verify") + " (SSL):"), self._verifyCheck)

        # Timeout double spinbox
        self._timeoutSpin = QDoubleSpinBox(self)
        self._timeoutSpin.setSuffix(" sec")
        self._timeoutSpin.setMinimum(3)
        self._timeoutSpin.setMaximum(300)
        layout.addRow(QLabel(_("Timeout") + ":"), self._timeoutSpin)

        # Limits: Max receive in KiB
        self._recvLimitSpin = QSpinBox(self)
        self._recvLimitSpin.setSuffix(" KiB")
        self._recvLimitSpin.setMinimum(1) # Min 1 KiB
        # Max 100 MiB (idk what json data or thumb is greater then this ..)
        self._recvLimitSpin.setMaximum(1024 * 100)
        layout.addRow(QLabel(_("Receive limit") + ":"), self._recvLimitSpin)

        # Chunk size in KiB
        self._chunkSizeSpin = QSpinBox(self)
        self._chunkSizeSpin.setSuffix(" KiB")
        self._chunkSizeSpin.setMinimum(1) # Min 1 KiB
        self._chunkSizeSpin.setMaximum(1024 * 50) # Max 50 MiB
        layout.addRow(QLabel(_("Chunk size") + ":"), self._chunkSizeSpin)

        # Proxy
        self._proxyWidget = ProxyWidget(self._model, self)
        layout.addRow(QLabel(_("Proxy") + ":"), self._proxyWidget)

        # Headers
        # User-agent
        self._useragents = UserAgentsEdit(model, self)
        layout.addRow(QLabel(_("User-Agents") + ":"), self._useragents)

        # Additional headers
        self._extraHeaders = RequestsHeaderEdit(model, self)
        layout.addRow(QLabel(_("Extra headers") + ":"), self._extraHeaders)

        # Init values for view
        self._changed()

        # Connections
        self._verifyCheck.stateChanged.connect(self.__verifyEdited)
        self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)
        self._recvLimitSpin.valueChanged.connect(self.__recvLimitEdited)
        self._chunkSizeSpin.valueChanged.connect(self.__chunkSizeEdited)

    def updateFromModel(self):
        self._proxyWidget.updateFromModel()
        self._useragents.updateFromModel()
        self._extraHeaders.updateFromModel()
        self._verifyCheck.stateChanged.disconnect(self.__verifyEdited)
        self._timeoutSpin.valueChanged.disconnect(self.__timeoutEdited)
        self._changed()
        self._verifyCheck.stateChanged.connect(self.__verifyEdited)
        self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)

    def __timeoutEdited(self, value):
        self._model.timeout = value

    def __verifyEdited(self, state):
        self._model.verifySSL = bool(state)

    def __recvLimitEdited(self, value):
        self._model.maxSize = int(value * 1024)

    def __chunkSizeEdited(self, value):
        self._model.chunkSize = int(value * 1024)

    def _changed(self):
        self._verifyCheck.setChecked(self._model.verifySSL)
        self._timeoutSpin.setValue(self._model.timeout)
        self._recvLimitSpin.setValue(int(self._model.maxSize / 1024))
        self._chunkSizeSpin.setValue(int(self._model.chunkSize / 1024))


class ImagesSettingsView(QFrame):
    def __init__(self, requestsModel, parent=None):
        QFrame.__init__(self, parent=parent)
        self._imgRequestsView = RequestsSettings(requestsModel, self)
        layout = QGridLayout(self)

        label = QLabel(_("Enable images"), self)
        self.__checkEnabled = QCheckBox(self)
        self.__checkEnabled.setChecked(ImagesSettings.enabled)
        layout.addWidget(label, 0, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__checkEnabled, 0, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Max threads"), self)
        self.__spinMaxThreads = QSpinBox(self)
        self.__spinMaxThreads.setSuffix(" threads")
        self.__spinMaxThreads.setMinimum(1)
        self.__spinMaxThreads.setMaximum(32)
        self.__spinMaxThreads.setValue(ImagesSettings.maxThreads)
        layout.addWidget(label, 1, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__spinMaxThreads, 1, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Max width"), self)
        self.__spinMaxWidth = QSpinBox(self)
        self.__spinMaxWidth.setSuffix(" px")
        self.__spinMaxWidth.setMinimum(32)
        self.__spinMaxWidth.setMaximum(10000)
        self.__spinMaxWidth.setValue(ImagesSettings.maxWidth)
        layout.addWidget(label, 2, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__spinMaxWidth, 2, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Max height"), self)
        self.__spinMaxHeight = QSpinBox(self)
        self.__spinMaxHeight.setSuffix(" px")
        self.__spinMaxHeight.setMinimum(32)
        self.__spinMaxHeight.setMaximum(10000)
        self.__spinMaxHeight.setValue(ImagesSettings.maxHeight)
        layout.addWidget(label, 3, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__spinMaxHeight, 3, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Fast transformation"), self)
        self.__checkFastTrans = QCheckBox(self)
        self.__checkFastTrans.setChecked(ImagesSettings.fastTransform)
        layout.addWidget(label, 4, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__checkFastTrans, 4, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("<h3>Image connection settings</h3>"), self)
        layout.addWidget(label, 5, 0, 1, 2, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self._imgRequestsView, 6, 0, 1, 2)

        layout.setRowStretch(6, 1)
        layout.setColumnStretch(1, 1)

        self.__checkEnabled.stateChanged.connect(self.__enabledEdited)
        self.__checkFastTrans.stateChanged.connect(self.__fastTransformEdited)
        self.__spinMaxThreads.valueChanged.connect(self.__maxThreadsEdited)
        self.__spinMaxWidth.valueChanged.connect(self.__maxWidthEdited)
        self.__spinMaxHeight.valueChanged.connect(self.__maxHeightEdited)

    def __enabledEdited(self, state):
        ImagesSettings.enabled = bool(state)

    def __fastTransformEdited(self, state):
        ImagesSettings.fastTransform = bool(state)

    def __maxThreadsEdited(self, value):
        ImagesSettings.maxThreads = value

    def __maxWidthEdited(self, value):
        ImagesSettings.maxWidth = value

    def __maxHeightEdited(self, value):
        ImagesSettings.maxHeight = value


class ImagesSettingsView(QFrame):
    def __init__(self, requestsModel, parent=None):
        """!
             @param requestsModel <class 'searxqt.models.settings.RequestSettingsModel'>
        """
        QFrame.__init__(self, parent=parent)
        self._requestsModel = requestsModel
        self._imgRequestsView = RequestsSettings(requestsModel, self)
        layout = QGridLayout(self)

        label = QLabel(_("Enable images"), self)
        self.__checkEnabled = QCheckBox(self)
        layout.addWidget(label, 0, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__checkEnabled, 0, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Max threads"), self)
        self.__spinMaxThreads = QSpinBox(self)
        self.__spinMaxThreads.setSuffix(" threads")
        self.__spinMaxThreads.setMinimum(1)
        self.__spinMaxThreads.setMaximum(32)
        layout.addWidget(label, 1, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__spinMaxThreads, 1, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Max thumb width"), self)
        self.__spinMaxWidth = QSpinBox(self)
        self.__spinMaxWidth.setSuffix(" px")
        self.__spinMaxWidth.setMinimum(32)
        self.__spinMaxWidth.setMaximum(10000)
        layout.addWidget(label, 2, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__spinMaxWidth, 2, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Max thumb height"), self)
        self.__spinMaxHeight = QSpinBox(self)
        self.__spinMaxHeight.setSuffix(" px")
        self.__spinMaxHeight.setMinimum(32)
        self.__spinMaxHeight.setMaximum(10000)
        layout.addWidget(label, 3, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__spinMaxHeight, 3, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("Fast transformation"), self)
        self.__checkFastTrans = QCheckBox(self)
        layout.addWidget(label, 4, 0, Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self.__checkFastTrans, 4, 1, Qt.AlignLeft|Qt.AlignTop)

        label = QLabel(_("<h3>Image connection settings</h3>"), self)
        layout.addWidget(label, 5, 0, 1, 2, Qt.AlignLeft|Qt.AlignTop)
        self.__comboConnection = QComboBox(self)
        self.__comboConnection.addItems([
            _("Use connection settings from the 'Connection' tab"),
            _("Custom connection settings for images")])
        layout.addWidget(self.__comboConnection, 6, 0, 1, 2)
        layout.addWidget(self._imgRequestsView, 7, 0, 1, 2)

        layout.setRowStretch(7, 1)
        layout.setColumnStretch(1, 1)

        self.__setValuesToView()

        self.__checkEnabled.stateChanged.connect(self.__enabledEdited)
        self.__checkFastTrans.stateChanged.connect(self.__fastTransformEdited)
        self.__spinMaxThreads.valueChanged.connect(self.__maxThreadsEdited)
        self.__spinMaxWidth.valueChanged.connect(self.__maxWidthEdited)
        self.__spinMaxHeight.valueChanged.connect(self.__maxHeightEdited)
        self.__comboConnection.currentIndexChanged.connect(self.__customProxyChanged)

    def __setValuesToView(self):
        self.__checkEnabled.setChecked(ImagesSettings.enabled)
        self.__spinMaxThreads.setValue(ImagesSettings.maxThreads)
        self.__spinMaxWidth.setValue(ImagesSettings.maxWidth)
        self.__spinMaxHeight.setValue(ImagesSettings.maxHeight)
        self.__checkFastTrans.setChecked(ImagesSettings.fastTransform)
        self.__comboConnection.setCurrentIndex(
            0 if self._requestsModel.useParent else 1
        )
        self.__customProxyChanged(0 if self._requestsModel.useParent else 1)

    def __enabledEdited(self, state):
        ImagesSettings.enabled = bool(state)

    def __fastTransformEdited(self, state):
        ImagesSettings.fastTransform = bool(state)

    def __maxThreadsEdited(self, value):
        ImagesSettings.maxThreads = value

    def __maxWidthEdited(self, value):
        ImagesSettings.maxWidth = value

    def __maxHeightEdited(self, value):
        ImagesSettings.maxHeight = value

    def __customProxyChanged(self, index):
        if index == 0:
            self._imgRequestsView.setEnabled(False)
            self._requestsModel.useParent = True
        else:
            self._imgRequestsView.setEnabled(True)
            self._requestsModel.useParent = False
            self._imgRequestsView.updateFromModel()

class SearxngSettings(QWidget):
    def __init__(self, model, parent=None):
        """
        model: SearchModel
        """
        QWidget.__init__(self, parent=parent)
        layout = QGridLayout(self)
        self.__searchModel = model

        self._useHtmlParser = QCheckBox(self)
        self._useHtmlParser.setChecked(model.parseHtml)

        self._safeSearch = QCheckBox(self)
        self._safeSearch.setChecked(model.safeSearch)

        infoLabel = QLabel(
            _("Since many SearXNG instances block API requests "
              "we now have a option to not use it and parse HTML "
              "response instead. Check \"Parse HTML\" below for "
              "that, when left unchecked it will use the API "
              "instead."), self)
        infoLabel.setWordWrap(True)

        layout.addWidget(infoLabel, 0, 0, 1, 2, Qt.AlignTop)

        layout.addWidget(QLabel(_("Parse HTML"), self), 1, 0,
                         Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self._useHtmlParser, 1, 1,
                         Qt.AlignLeft|Qt.AlignTop)

        infoLabel = QLabel(_("Enable \"Safe search\" for search engines that "
                             "support it."), self)
        infoLabel.setWordWrap(True)
        layout.addWidget(infoLabel, 2, 0, 1, 2, Qt.AlignTop)

        layout.addWidget(QLabel(_("Safe Search"), self), 3, 0,
                         Qt.AlignLeft|Qt.AlignTop)
        layout.addWidget(self._safeSearch, 3, 1, Qt.AlignLeft|Qt.AlignTop)

        layout.setRowStretch(3, 1)
        layout.setColumnStretch(1, 1)

        self._useHtmlParser.stateChanged.connect(self.__useHtmlParserChanged)
        self._safeSearch.stateChanged.connect(self.__safeSearchChanged)

    def __useHtmlParserChanged(self, state):
        self.__searchModel.parseHtml = bool(state)

    def __safeSearchChanged(self, state):
        self.__searchModel.safeSearch = bool(state)


class Stats2Settings(QWidget):  # TODO rename to SearxSpaceSettings and rename stats2 to searxspace or something..
    def __init__(self, model, parent=None):
        """
        @type model: SearxStats2Model
        """
        QWidget.__init__(self, parent=parent)

        self._model = model

        layout = QVBoxLayout(self)

        infoLabel = QLabel(_(
            "The Searx-Stats2 project lists public Searx instances with"
            " statistics. The original instance is running at"
            " https://searx.space/. This is where Searx-Qt will request"
            " a list with instances when the update button is pressed."),
            self
        )
        infoLabel.setWordWrap(True)

        layout.addWidget(infoLabel, 0, Qt.AlignTop)

        hLayout = QHBoxLayout()

        label = QLabel("URL:", self)
        label.setSizePolicy(
            QSizePolicy(
                QSizePolicy.Maximum, QSizePolicy.Maximum
            )
        )
        self._urlLabel = QLabel(model.url, self)
        self._urlEditButton = Button(_("Edit"), self)
        self._urlResetButton = Button(_("Reset"), self)

        hLayout.addWidget(label, 0, Qt.AlignTop)
        hLayout.addWidget(self._urlLabel, 0, Qt.AlignTop)
        hLayout.addWidget(self._urlEditButton, 0, Qt.AlignTop)
        hLayout.addWidget(self._urlResetButton, 0, Qt.AlignTop)

        spacer = QSpacerItem(
            20, 40, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding
        )

        layout.addLayout(hLayout)
        layout.addItem(spacer)

        self._urlEditButton.clicked.connect(self.__urlEditClicked)
        self._urlResetButton.clicked.connect(self.__urlResetClicked)
        model.changed.connect(self.__modelChanged)

    def __modelChanged(self):
        self._urlLabel.setText(self._model.url)

    def __urlEditClicked(self):
        dialog = UrlDialog(self._model.url)
        if dialog.exec():
            self._model.url = dialog.url

    def __urlResetClicked(self):
        self._model.reset()


class LogLevelSettings(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent=parent)

        layout = QVBoxLayout(self)

        label = QLabel(_("<h2>CLI output level</h2>"), self)
        self.__cbInfo = QCheckBox(_("Info"), self)
        self.__cbWarning = QCheckBox(_("Warning"), self)
        self.__cbDebug = QCheckBox(_("Debug"), self)
        self.__cbError = QCheckBox(_("Error"), self)

        if log.LogLevel & log.LogLevels.INFO:
            self.__cbInfo.setChecked(True)

        if log.LogLevel & log.LogLevels.WARNING:
            self.__cbWarning.setChecked(True)

        if log.LogLevel & log.LogLevels.DEBUG:
            self.__cbDebug.setChecked(True)

        if log.LogLevel & log.LogLevels.ERROR:
            self.__cbError.setChecked(True)

        layout.addWidget(label)
        if log.DebugMode == True:
            label = QLabel(
                _("Debug mode enabled via environment variable"
                  " 'SEARXQT_DEBUG'. The settings below are ignored,"
                  " unset 'SEARXQT_DEBUG' and restart Searx-Qt to disable"
                  " debug mode."),
                parent=self
            )
            label.setWordWrap(True)
            layout.addWidget(label)
        layout.addWidget(self.__cbInfo)
        layout.addWidget(self.__cbWarning)
        layout.addWidget(self.__cbDebug)
        layout.addWidget(self.__cbError)

        self.__cbInfo.stateChanged.connect(self.__stateChangedInfo)
        self.__cbWarning.stateChanged.connect(self.__stateChangedWarning)
        self.__cbDebug.stateChanged.connect(self.__stateChangedDebug)
        self.__cbError.stateChanged.connect(self.__stateChangedError)

    def __stateChanged(self, logLevel, state):
        if state:
            log.LogLevel |= logLevel
        else:
            log.LogLevel &= ~logLevel

    def __stateChangedInfo(self, state):
        self.__stateChanged(log.LogLevels.INFO, state)

    def __stateChangedWarning(self, state):
        self.__stateChanged(log.LogLevels.WARNING, state)

    def __stateChangedDebug(self, state):
        self.__stateChanged(log.LogLevels.DEBUG, state)

    def __stateChangedError(self, state):
        self.__stateChanged(log.LogLevels.ERROR, state)


class CustomAnchorCmdSettings(QFrame):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent=parent)
        layout = QGridLayout(self)

        # Title
        layout.addWidget(
            QLabel(f"<h2>{_('Custom anchor commands')}</h2>", self),
            0, 0, 1, 2, Qt.AlignTop | Qt.AlignLeft)

        # Info
        infoText = _("When the custom command for a scheme is not enabled, "
                     "QDesktopServices will do its thing. '%url' will be "
                     "replaced with the url.")
        layout.addWidget(QLabel(infoText, self)              , 1, 0, 1, 2)

        # Table headers
        layout.addWidget(QLabel(_("<b>Enabled</b>"), self), 2, 0, 1, 1)
        layout.addWidget(QLabel(_("<b>Command</b>"), self), 2, 1, 1, 1)

        self.__checkHttp = QCheckBox("HTTP/HTTPS", self)
        self.__checkFtp = QCheckBox("FTP", self)
        self.__checkMagnet = QCheckBox("Magnet", self)

        self.__editHttp = QLineEdit(self)
        self.__editFtp = QLineEdit(self)
        self.__editMagnet = QLineEdit(self)

        self.__editHttp.setPlaceholderText("iceweasel %url")
        self.__editFtp.setPlaceholderText("iceweasel %url")
        self.__editMagnet.setPlaceholderText("deluge %url")

        self.__editHttp.setEnabled(False)
        self.__editFtp.setEnabled(False)
        self.__editMagnet.setEnabled(False)

        layout.addWidget(self.__checkHttp                    , 3, 0, 1, 1)
        layout.addWidget(self.__editHttp                     , 3, 1, 1, 1)

        layout.addWidget(self.__checkFtp                     , 4, 0, 1, 1)
        layout.addWidget(self.__editFtp                      , 4, 1, 1, 1)

        layout.addWidget(self.__checkMagnet                  , 5, 0, 1, 1)
        layout.addWidget(self.__editMagnet                   , 5, 1, 1, 1)

        layout.setColumnStretch(0, 0)
        layout.setColumnStretch(1, 1)

        # Connections
        self.__checkHttp.stateChanged.connect(self.__checkChangedHttp)
        self.__checkFtp.stateChanged.connect(self.__checkChangedFtp)
        self.__checkMagnet.stateChanged.connect(self.__checkChangedMagnet)

        self.__editHttp.textEdited.connect(self.__cmdEditedHttp)
        self.__editFtp.textEdited.connect(self.__cmdEditedFtp)
        self.__editMagnet.textEdited.connect(self.__cmdEditedMagnet)

        # Set values (after connections so stuff gets triggered)
        self.__checkHttp.setChecked(AnchorCMD.http.enabled)
        self.__checkFtp.setChecked(AnchorCMD.ftp.enabled)
        self.__checkMagnet.setChecked(AnchorCMD.magnet.enabled)

        self.__editHttp.setText(AnchorCMD.http.cmd)
        self.__editFtp.setText(AnchorCMD.ftp.cmd)
        self.__editMagnet.setText(AnchorCMD.magnet.cmd)

    def __checkChangedHttp(self, state):
        state = bool(state)
        AnchorCMD.http.enabled = state
        self.__editHttp.setEnabled(state)

    def __checkChangedFtp(self, state):
        state = bool(state)
        AnchorCMD.ftp.enabled = state
        self.__editFtp.setEnabled(state)

    def __checkChangedMagnet(self, state):
        state = bool(state)
        AnchorCMD.magnet.enabled = state
        self.__editMagnet.setEnabled(state)

    def __cmdEditedHttp(self, newCmd):
        AnchorCMD.http.cmd = newCmd

    def __cmdEditedFtp(self, newCmd):
        AnchorCMD.ftp.cmd = newCmd

    def __cmdEditedMagnet(self, newCmd):
        AnchorCMD.magnet.cmd = newCmd

class GeneralSettings(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent=parent)
        layout = QVBoxLayout(self)

        # Theme
        label = QLabel(f"<h2>{_('Theme')}</h2>", self)
        layout.addWidget(label, 0, Qt.AlignTop)

        formLayout = QFormLayout()
        layout.addLayout(formLayout)

        self.__themesCombo = QComboBox(self)
        currentTheme = Themes.currentTheme
        indexOfCurrentTheme = 0
        index = 1
        self.__themesCombo.addItem("None", QVariant(None))
        for theme in Themes.themes:
            data = QVariant(theme)
            self.__themesCombo.addItem(theme.name, data)

            if theme.key == currentTheme:
                indexOfCurrentTheme = index

            index += 1
        self.__themesCombo.setCurrentIndex(indexOfCurrentTheme)
        formLayout.addRow(
            QLabel(_("Theme:"), self),
            self.__themesCombo
        )

        self.__stylesCombo = QComboBox(self)
        currentStyle = Themes.currentStyle
        indexOfCurrentStyle = 0
        index = 0
        for style in Themes.styles:
            self.__stylesCombo.addItem(style, QVariant(style))

            if style == currentStyle:
                indexOfCurrentStyle = index

            index += 1

        self.__stylesCombo.setCurrentIndex(indexOfCurrentStyle)

        formLayout.addRow(
            QLabel(_("Base style:"), self),
            self.__stylesCombo
        )

        applyButton = Button("Apply", self)
        applyButton.clicked.connect(self.__applyTheme)
        layout.addWidget(applyButton, 0, Qt.AlignTop)

        # Log level
        logLevelSettings = LogLevelSettings(self)
        layout.addWidget(logLevelSettings, 0, Qt.AlignTop)

        # Custom anchor commands
        customAnchorCmds = CustomAnchorCmdSettings(self)
        layout.addWidget(customAnchorCmds, 1, Qt.AlignTop)

    def __applyTheme(self):
        index = self.__stylesCombo.currentIndex()
        style = self.__stylesCombo.itemData(index, Qt.UserRole)
        Themes.setStyle(style)

        index = self.__themesCombo.currentIndex()
        theme = self.__themesCombo.itemData(index, Qt.UserRole)
        Themes.setTheme(theme.key if theme is not None else "")

        Themes.repolishAllWidgets()


class SettingsWindow(QTabWidget):
    closed = pyqtSignal()

    def __init__(self, model, searchModel, guard, parent=None):
        """
        @type model: SettingsModel
        """
        QTabWidget.__init__(self, parent=parent)
        self.setWindowTitle(_("Settings"))

        # General settings
        self._generalView = GeneralSettings(self)
        self.addTab(self._generalView, _("General"))

        # Requests settings
        self._requestsView = RequestsSettings(model.requests, self)
        self.addTab(self._requestsView, _("Connection"))

        # Image Requests settings
        if ImagesSettings.supported:
            self._imagesView = ImagesSettingsView(model.imgRequests, self)
        else:
            self._imagesView = QLabel(_("PIL not found on system. No image support."), self);
        self.addTab(self._imagesView, _("Images"))

        # SearXNG settings
        self._searxView = SearxngSettings(searchModel, self)
        self.addTab(self._searxView, "SearXNG")

        # Stats2 settings
        if model.stats2:
            self._stats2View = Stats2Settings(model.stats2, self)
            self.addTab(self._stats2View, "Searx-Stats2")

        # Guard settings
        self._guardView = GuardSettings(guard, self)
        self.addTab(self._guardView, _("Guard"))

    def closeEvent(self, event):
        QTabWidget.closeEvent(self, event)
        self.closed.emit()
